package com.xl.rentkeeper.log;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.util.Log;

import com.xl.rentkeeper.App;
import com.xl.rentkeeper.PathConf;
import com.xl.rentkeeper.business.UserLogic;
import com.xl.rentkeeper.util.AndroidUtil;
import com.xl.rentkeeper.util.ZipUtil;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;


public class QLog {
    public static final String TAG = "QLOG";
    protected static boolean isLogToFile = true;

    protected static Object formatterLock = new Object();


    private static String logPath = "";

    private static long nextHourTime;

    private static long nextSecondMinuteTime;

    private static String processName = "";

    private static String packageName = "";

    private static FileWriter writer;

    static MyLinkedBlockingDeque<String> logDeque = new MyLinkedBlockingDeque<String>(15000);

    static final ReentrantLock lock = new ReentrantLock();

    private static AtomicBoolean isInitLogFileDone = new AtomicBoolean(false);

    private static int myProcessId;

    private static AtomicBoolean isPreExceptionEnospc = new AtomicBoolean(false);

    public final static java.text.SimpleDateFormat timeFormatter = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
    /*
     * VALUES WHICH USED TO RETRY TO INIT QLOG WHEN INIT FAILED
     */
    private static final int[] INTERVAL_RETRY_INIT = new int[]{1, 2, 4, 8, 16, 29}; //minute
    private static AtomicInteger retryInitTimes = new AtomicInteger(0);
    private static Handler retryInitHandler = new Handler(Looper.getMainLooper());
    private static volatile Context sContext = null;

    public static void init(Context context) {
        sContext = context.getApplicationContext();

        acutualInitRunnable.run();
    }

    public static Runnable acutualInitRunnable = new Runnable() {
        public void run() {
            if (sContext == null || !isLogToFile || isInitLogFileDone.get()) {
                return;
            }
            new Thread("QLogInitThread") {
                public void run() {
                    if (isLogToFile && isInitLogFileDone.compareAndSet(false, true)) {
                        try {
                            processName = AndroidUtil.getProcessName(sContext);
                            try {
                                packageName = sContext.getPackageName();
                            } catch (Exception e) {
                                packageName = "unknow";
                            }
                            myProcessId = Process.myPid();
                            // load uin Level
                            initLogFile(System.currentTimeMillis());
                            t.setName("logWriteThread");
                            t.start();
                            retryInitHandler.removeCallbacks(acutualInitRunnable);
                        } catch (Exception e) {
                            isInitLogFileDone.set(false);
                            e.printStackTrace();
                            int times = retryInitTimes.get();
                            retryInitHandler.removeCallbacks(acutualInitRunnable);
                            retryInitHandler.postDelayed(acutualInitRunnable, INTERVAL_RETRY_INIT[times] * 60000);
                            times++;
                            if (times >= INTERVAL_RETRY_INIT.length) {
                                times = 0;
                            }
                            retryInitTimes.set(times);
                        }
                    }
                }

                ;
            }.start();
        }
    };

    private static String getThisHour(long nowCurrentMillis) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(nowCurrentMillis);
        SimpleDateFormat logFileFormatter = new SimpleDateFormat("yy.MM.dd.HH");
        logTime = timeFormatter.format(nowCurrentMillis);
        String thisHourLogName = logFileFormatter.format(calendar.getTime());
        setNextSecond(calendar);
        setNextHour(calendar);
        return thisHourLogName;
    }

    private static void setNextHour(Calendar setSecondedCalendar) {
        setSecondedCalendar.add(Calendar.HOUR_OF_DAY, 1);
        setSecondedCalendar.set(Calendar.MINUTE, 0);
        setSecondedCalendar.set(Calendar.SECOND, 0);
        nextHourTime = setSecondedCalendar.getTimeInMillis();
    }

    private static void setNextSecond(Calendar calendar) {
        calendar.set(Calendar.MILLISECOND, 0);
        nextSecondMinuteTime = calendar.getTimeInMillis() + 1000;
    }

    private static String logTime = "";

    private static synchronized void checkNextMinuteTime(long currentTimeMillis) {
        if (currentTimeMillis > nextSecondMinuteTime) {
            synchronized (formatterLock) {
                logTime = timeFormatter.format(currentTimeMillis);
                nextSecondMinuteTime = nextSecondMinuteTime + 1000;
            }
        }
    }

    public static String getLogFileName(String hourTime) {
        String[] splits = processName.split(":");
        String proc = "main";
        if (splits.length > 1) {
            proc = splits[1];
        }
        return proc + "." + hourTime + ".log";
    }

    static String nowUsedFile = "";


    static synchronized void initLogFile(long nowCurrentTimeMillis) throws IOException {
        logPath = PathConf.APP_ROOT_PATH + "log/";
        File tmpeFile = new File(logPath);
        if (!tmpeFile.exists()) {
            tmpeFile.mkdirs();
        }
        nowUsedFile = logPath + getLogFileName(getThisHour(nowCurrentTimeMillis));
        try {
            tmpeFile = new File(nowUsedFile);
            if (!tmpeFile.exists()) {
                boolean b = tmpeFile.createNewFile();
                writeAppVersion();  //写入旧文件结尾
                if (null != writer) {
                    writer.write(getPrintMsg(TAG, android.os.Build.MODEL + " " + android.os.Build.VERSION.RELEASE + " create newLogFile " + tmpeFile.getName() + " " + b, null));
                    writer.flush();
                }
            } else {
                writeAppVersion();
                if (null != writer) {
                    writer.write(getPrintMsg(TAG, android.os.Build.MODEL + " " + android.os.Build.VERSION.RELEASE + "|newLogFile " + tmpeFile.getName() + " is existed.", null));
                    writer.flush();
                }
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        writer = new FileWriter(tmpeFile, true);
        writeAppVersion(); //写入新文件开始
    }

    private static void writeAppVersion() throws IOException {
        if (null != writer) {
//			if(!"".equals(QLog.sBuildNumber)){//印出版本号
//				 writer.write(logTime + "|" + processName + "|D|" +  "|QQ_Version: "+QLog.sBuildNumber+"\r\n");
//				 writer.flush();
//			 }
        }
    }

    static long lastWriterErrorTime = 0;

    public static String getPrintMsg(String tag, String msg, Throwable tr) {
        long threadId = Thread.currentThread().getId();
        String threadName = Thread.currentThread().getName();
        String message = logTime + " " + myProcessId + " t " + threadName + " " + String.valueOf(threadId) + " " + tag + " " + msg + "\n";
        StringBuilder sb = new StringBuilder();
        sb.append(logTime);
        sb.append(" ");
        sb.append(myProcessId);
        sb.append(" t ");
        sb.append(threadName);
        sb.append("[");
        sb.append(threadId);
        sb.append("] ");
        sb.append(tag);
        sb.append(" ");
        sb.append(msg);
        sb.append("\n");
        return sb.toString();
    }

    /**
     * 把日志信息放到缓冲队列中，
     * 异步在低优先级线程中去写缓存队列的消息，
     * 避免IO堵塞UI
     *
     * @param tag 日志的级别，请参考ReportLevel
     * @param msg 日志的消息体
     * @param tr
     */
    private static void addLogItem(String tag, String msg, Throwable tr) {
        long threadId = Thread.currentThread().getId();
        String threadName = Thread.currentThread().getName();
        if (tr == null)
            Log.d(tag, threadName + "[" + String.valueOf(threadId) + "] " + msg);
        else
            Log.d(tag, threadName + "[" + String.valueOf(threadId) + "] " + msg, tr);
        String message = getPrintMsg(tag, msg, tr);
        if (!isLogToFile) {
            return;
        }
        long now = System.currentTimeMillis();
        if (now >= nextSecondMinuteTime) {
            checkNextMinuteTime(now);
        }
        if (null != tr) {
            message = message + "\n" + Log.getStackTraceString(tr) + "\n";
        }
        if (!addLogToCache(message)) {
            Log.d("QLog", "addLogToCache failed!");
            return;
        }
    }


    private static boolean addLogToCache(String log) {
        try {
            logDeque.add(log);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private static boolean insertLogToCacheHead(String log) {
        try {
            logDeque.addFirst(log);
            return true;
        } catch (Exception e) {
            return false;
        }
    }


    private static void writeLogToFile(String log) {
        // 如果SD卡不可用,则不写日志,以免每次都抛出异常,影响性能
        if (!isLogToFile || !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            return;
        }
        try {
            if (null == writer) {
                System.out.println("can not write log.");
                long now = System.currentTimeMillis();
                if (lastWriterErrorTime == 0) {
                    lastWriterErrorTime = now;
                } else if (now - lastWriterErrorTime > 60 * 1000) {
                    try {
                        initLogFile(System.currentTimeMillis());
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                    lastWriterErrorTime = now;
                }
            } else {
                long now = System.currentTimeMillis();
                if (now > nextHourTime) {
                    initLogFile(now);
                }
                //加入消息的时候记录时间
                if (lock.tryLock()) {
                    try {
                        writer.write(log);
                        writer.flush();
                    } finally {
                        lock.unlock();
                    }
                } else {
                    if (!insertLogToCacheHead(log)) {
                        Log.d("QLog", "insertLogToCacheHead failed!");
                    }
                }
            }
            isPreExceptionEnospc.compareAndSet(true, false);
        } catch (Throwable e) {
            if (e instanceof IOException && e.getMessage().contains("ENOSPC")) {
                if (isPreExceptionEnospc.compareAndSet(false, true)) {
                    e.printStackTrace();
                }
            } else {
                isPreExceptionEnospc.compareAndSet(true, false);
                e.printStackTrace();
                try {
                    initLogFile(System.currentTimeMillis());
                } catch (Throwable e1) {
                    e1.printStackTrace();
                }
            }
        }
    }

    static Thread t = new Thread() {
        public void run() {
            while (true) {
                try {
                    String log = logDeque.take();
                    if (null != log) {
                        writeLogToFile(log);
                    }
                } catch (InterruptedException e) {
                    System.out.println("write log file error." + e);
                }
            }
        }
    };

    public static void d(String tag, String msg) {
        d(tag, msg, null);
    }

    public static void d(String tag, String msg, Throwable tr) {
        addLogItem(tag, msg, tr);
    }

    public static void d(Object obj, String msg) {
        d(obj.getClass().getSimpleName(), msg);
    }

    public static void d(Object obj, String msg, Throwable tr) {
        d(obj.getClass().getSimpleName(), msg, tr);
    }



    public static void reportLog(final long startTime, final long endTime, final ZipResultCallback callback) {
        SimpleDateFormat logFileFormatter = new SimpleDateFormat("yy.MM.dd.HH");
        List<String> fileList = new ArrayList<>();
        long tmpTime = startTime;
        while (true) {
            String thisHourLogName = logFileFormatter.format(tmpTime) + ".log";
            fileList.add(thisHourLogName);
            tmpTime = tmpTime + 1 * 60 * 60 * 1000;
            if (tmpTime > endTime) {
                break;
            }
        }
        if (fileList.size() == 0) {
            callback.zipFail("该时间段没有日志");
            return;
        }
        File[] files = new File(logPath).listFiles();
        if (files != null && files.length > 0) {
            ArrayList<File> logFiles = new ArrayList<File>();
            for (File file : files) {
                for (String fileName : fileList) {
                    if (file.getName().endsWith(fileName)) {
                        logFiles.add(file);
                    }
                }
            }
            if(logFiles.size()==0){
                callback.zipFail("该时间段没有日志");
                return;
            }
            logTime = logTime.replace(":", "_");
            String name = logFileFormatter.format(new Date());
            String zipFileName =  logPath + UserLogic.getInstance().getUid()+"_" + AndroidUtil.getIMEI(App.getApp())+ "_" + name + "_"+(new Random().nextInt(100))+".zip";
            File zipFile = new File(zipFileName);
            zipFile.delete();
            try {
                zipFile.createNewFile();
                ZipUtil.zipFiles(logFiles, zipFileName);
            } catch (IOException e) {
                callback.zipFail("压缩失败");
                QLog.d(TAG, "doReportLogSelf exception " + e);
            } catch (Throwable e) {
                callback.zipFail("压缩失败");
                QLog.d(TAG, "doReportLogSelf error " + e);
            }
            callback.zipOk(zipFileName);
        }else{
            callback.zipFail("未发现日志文件");
        }
    }
}
